﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Globalization;
using System.IO;

namespace DiracTCC.Business
{
    public class XmlAnalyser
    {
        XElement xelement;

        public XmlAnalyser(string path)
        {
            xelement = XElement.Load(path);
        }

        public int GetNumberOfFrames()
        {
            return xelement.Elements("frames").Elements("frame").Count();
        }

        public IEnumerable<GraphPoint> GetMVSizeHistogram(int frameNumber, int referenceNum)
        {
            var xmlPoints =
                xelement.Elements("frames").Elements("frame")
                .First(x => x.Attribute("pnum").Value.Equals(frameNumber.ToString())).Element("mvs").Element("vector")
                .Elements("ref").Where(x => x.Attribute("num").Value.Equals(referenceNum.ToString()));

            var points = from p in xmlPoints.First().Elements("vector")
                         select new GraphPoint
                         {
                             x = Math.Sqrt(Math.Pow(double.Parse(p.Attribute("mv_x").Value.ToString()), 2) + Math.Pow(double.Parse(p.Attribute("mv_x").Value.ToString()), 2)),
                             y = 1
                         };

            return GraphDataProcessor.GetHistogram(points, 10);
        }

        public VideoInfo GetVideoInfo()
        {
            var info =
                xelement.Element("header").Element("info");

            VideoInfo video = new VideoInfo
            {
                Chroma = DiracReaderHelper.GetChromaFormat(int.Parse(info.Attribute("chroma").Value)),
                FrameRate = new int[2] {int.Parse(info.Attribute("frame_rate_numerator").Value.Trim()), int.Parse(info.Attribute("frame_rate_denominator").Value.Trim())},
                PictureCodingMode = (info.Attribute("picture_coding_mode").Value == "0") ? PictureCodingMode.Frames : PictureCodingMode.Fields,
                PixelAspectRatio = new int[2] {int.Parse(info.Attribute("pix_asr_numerator").Value.Trim()), int.Parse(info.Attribute("pix_asr_denominator").Value.Trim())},
                SourceSampling = (info.Attribute("source_sampling").Value == "0") ? SourceSampling.Progressivo : SourceSampling.Entrelacado,
                TopFieldFirst = (info.Attribute("topfieldfirst").Value == "0") ? TopFieldFirst.BottonFieldFirst : TopFieldFirst.TopFieldFirst,
                VideoSize = new int[2] {int.Parse(info.Attribute("src_width").Value.Trim()), int.Parse(info.Attribute("src_height").Value.Trim())},
                NumberOfFrames = GetNumberOfFrames(),
                NumL1 = int.Parse(info.Attribute("num_l1").Value.Trim()),
                L1Sep = int.Parse(info.Attribute("l1_sep").Value.Trim()),
                QF = double.Parse(info.Attribute("qf").Value.Trim(), new CultureInfo("en-US")),
            };

            return video;
        }

        public FrameInfo GetFrameInfo(int frameNumber)
        {
            var frameInfo = new FrameInfo();
            frameInfo.FrameNumber = frameNumber;

            var xmlPoints =
                xelement.Elements("frames").Elements("frame").Where(x => x.Attribute("pnum").Value == frameNumber.ToString());

            // frame data
            if (xmlPoints.Count() == 0)
                return frameInfo;

            var frame = xmlPoints.First();

            frameInfo.PictureType =
                DiracReaderHelper.GetPictureType(frame.Attribute("ptype").Value);

            if (frameInfo.PictureType == "INTER_PICTURE")
            {
                frameInfo.BlockSeparation = string.Format("{0} x {1}",
                    frame.Attribute("xbsep").Value,
                    frame.Attribute("ybsep").Value);

                frameInfo.MVLenght = string.Format("{0} x {1}",
                    frame.Attribute("mv_xlen").Value,
                    frame.Attribute("mv_ylen").Value);

                frameInfo.SubBlockLenght = string.Format("{0} x {1}",
                    frame.Attribute("sb_xlen").Value,
                    frame.Attribute("sb_ylen").Value);
            }
            else
            {
                return frameInfo;
            }

            // references
            var references = xmlPoints.First().Element("references");

            int numOfReferences = int.Parse(references.Attribute("num_refs").Value);

            if (numOfReferences > 0)
                frameInfo.Ref1 = int.Parse(references.Elements("ref").ElementAt(0).Attribute("pnum").Value);

            if (numOfReferences > 1)
                frameInfo.Ref2 = int.Parse(references.Elements("ref").ElementAt(1).Attribute("pnum").Value);

            return frameInfo;
        }

        public SubBlockArray GetSubBlockArray(int frameNumber)
        {
            SubBlockArray array = new SubBlockArray();
            var xmlPoints =
                xelement.Elements("frames").Elements("frame").Where(x => x.Attribute("pnum").Value == frameNumber.ToString());

            // frame data
            if (xmlPoints.Count() == 0 || xmlPoints.First().Attribute("ptype").Value == "intra")
                return array;

            var sbs = xmlPoints.First().Element("sbs").Elements("sb");
            foreach (var item in sbs)
            {
                int x = int.Parse(item.Attribute("x").Value);
                int y = int.Parse(item.Attribute("y").Value);
                var splitMode = (SubBlockSplitMode)Enum.ToObject(typeof(SubBlockSplitMode), int.Parse(item.Attribute("sb_split_mode").Value));
                SubBlockPredictionMode[] predictioMode = GetPredictionMode(x, y, splitMode, xmlPoints);

                array.AddSubBlockInfo(
                    x, y, double.Parse(item.Attribute("sb_costs").Value), 
                    splitMode, predictioMode);
            }

            return array;
        }

        private SubBlockPredictionMode[] GetPredictionMode(int x, int y, SubBlockSplitMode splitMode, IEnumerable<XElement> xmlPoints)
        {
            SubBlockPredictionMode[] predArray;
            int i, j;

            switch (splitMode)
            {
                case SubBlockSplitMode.None:
                    predArray = new SubBlockPredictionMode[1];
                    predArray[0] = 
                        (SubBlockPredictionMode)Enum.ToObject(typeof(SubBlockPredictionMode),
                        int.Parse(xmlPoints.First().Element("mvs").Element("data").Elements("mv").First(a => a.Attribute("x").Value == (x * 4).ToString() && a.Attribute("y").Value == (y * 4).ToString()).Attribute("pred_mode").Value));
                    return predArray;
                    break;
                case SubBlockSplitMode.Sub2x2:
                    predArray = new SubBlockPredictionMode[4];
                    i = 0; j = 0;
                    for (int k = 0; k < 4; k++)
                    {
                        predArray[k] =
                            (SubBlockPredictionMode)Enum.ToObject(typeof(SubBlockPredictionMode),
                            int.Parse(xmlPoints.First().Element("mvs").Element("data").Elements("mv").First(a => a.Attribute("x").Value == (x * 4 + i * 2).ToString() && a.Attribute("y").Value == (y * 4 + j * 2).ToString()).Attribute("pred_mode").Value));

                        i++;
                        if (i == 2)
                        {
                            i = 0;
                            j++;
                        }
                    }
                    return predArray;
                    break;
                case SubBlockSplitMode.Sub4x4:
                    predArray = new SubBlockPredictionMode[16];
                    i = 0; j = 0;
                    for (int k = 0; k < 16; k++)
                    {
                        predArray[k] =
                            (SubBlockPredictionMode)Enum.ToObject(typeof(SubBlockPredictionMode),
                            int.Parse(xmlPoints.First().Element("mvs").Element("data").Elements("mv").First(a => a.Attribute("x").Value == (x * 4 + i).ToString() && a.Attribute("y").Value == (y * 4 + j).ToString()).Attribute("pred_mode").Value));

                        i++;
                        if (i == 4)
                        {
                            i = 0;
                            j++;
                        }
                    }
                    return predArray;
                    break;
            }
            return null;
        }

        public List<GraphPoint> GetSubBlockCosts(int frameNumber)
        {
            var array = new List<GraphPoint>();
            var xmlPoints =
                xelement.Elements("frames").Elements("frame").Where(x => x.Attribute("pnum").Value == frameNumber.ToString());

            // frame data
            if (xmlPoints.Count() == 0 || xmlPoints.First().Attribute("ptype").Value == "intra")
                return array;

            var sbs = xmlPoints.First().Elements("sbs").First().Elements("sb");
            CultureInfo culture = new CultureInfo("en-US");

            foreach (var item in sbs)
            {
                array.Add(new GraphPoint
                {
                    x = int.Parse(item.Attribute("x").Value, culture.NumberFormat),
                    y = int.Parse(item.Attribute("y").Value, culture.NumberFormat),
                    z = double.Parse(item.Attribute("sb_costs").Value, culture.NumberFormat)
                });
            }

            return array;
        }

        public List<GraphPoint> GetMVVectorsSize(int frameNumber, int refPosition)
        {
            var array = new List<GraphPoint>();
            var xmlPoints =
                xelement.Elements("frames").Elements("frame").Where(x => x.Attribute("pnum").Value == frameNumber.ToString());

            // frame data
            if (xmlPoints.Count() == 0)
                return array;

            List<XElement> mvs = null;
            if (refPosition == 0)
            {
                mvs = xmlPoints.First().Elements("mvs").First().Elements("vector").First().Elements("ref").First().Elements("vector").ToList();
            }
            else
            {
                mvs = xmlPoints.First().Elements("mvs").First().Elements("vector").First().Elements("ref").Last().Elements("vector").ToList();
            }
            CultureInfo culture = new CultureInfo("en-US");

            foreach (var item in mvs)
            {
                array.Add(new GraphPoint
                {
                    x = int.Parse(item.Attribute("i").Value, culture.NumberFormat),
                    y = int.Parse(item.Attribute("j").Value, culture.NumberFormat),
                    z = Math.Sqrt(
                        Math.Pow(double.Parse(item.Attribute("mv_x").Value, culture.NumberFormat), 2) + 
                        Math.Pow(double.Parse(item.Attribute("mv_y").Value, culture.NumberFormat), 2))
                });
            }

            var histogram = new List<GraphPoint>();
            foreach (var value in array.Select(a => a.z).Distinct())
            {
                histogram.Add(new GraphPoint
                {
                    x = value,
                    y = array.Count(a => a.z == value)
                });
            }

            return histogram;
        }

        public List<GraphPoint> GetMVCosts(int frameNumber, int refPosition)
        {
            var array = new List<GraphPoint>();
            var xmlPoints =
                xelement.Elements("frames").Elements("frame").Where(x => x.Attribute("pnum").Value == frameNumber.ToString());

            // frame data
            if (xmlPoints.Count() == 0)
                return array;

            List<XElement> mvs = null;
            if (refPosition == 0)
            {
                mvs = xmlPoints.First().Elements("mvs").First().Elements("vector").First().Elements("ref").First().Elements("vector").ToList();
            }
            else
            {
                mvs = xmlPoints.First().Elements("mvs").First().Elements("vector").First().Elements("ref").Last().Elements("vector").ToList();
            }
            CultureInfo culture = new CultureInfo("en-US");

            foreach (var item in mvs)
            {
                array.Add(new GraphPoint
                {
                    x = int.Parse(item.Attribute("i").Value, culture.NumberFormat),
                    y = int.Parse(item.Attribute("j").Value, culture.NumberFormat),
                    z = double.Parse(item.Attribute("sad").Value, culture.NumberFormat)
                });
            }

            return array;
        }
    }
}
